package com.mylater.nace.server;

import org.jboss.netty.bootstrap.Bootstrap;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

/**
 * The AbstractNettyServer represents
 *
 * @author fengjiachun
 * @version $Id$
 */
public abstract class AbstractNettyServer implements NettyServer {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractNettyServer.class);
    public static final ChannelGroup ALL_CHANNELS = new DefaultChannelGroup("SERVER-CHANNELS");
    protected int portNumber = 18090;
    protected InetSocketAddress socketAddress;
    protected Bootstrap serverBootstrap;
    protected ChannelPipelineFactory pipelineFactory;

    @Override
    public void stopServer() throws Exception {
        if (LOG.isDebugEnabled()) {
            LOG.debug("In stopServer method of class: {}", this.getClass().getName());
        }
        ChannelGroupFuture future = ALL_CHANNELS.close();
        try {
            future.await();
        } catch (InterruptedException e) {
            LOG.error("Execption occurred while waiting for channels to close: {}", e);
        }
        this.serverBootstrap.releaseExternalResources();
    }

    @Override
    public void configureServerBootStrap(String[] optionsList) {
        // For clients who do not use spring.
        if (null == this.serverBootstrap) {
            createServerBootstrap();
        }
        this.serverBootstrap.setPipelineFactory(this.pipelineFactory);
        if (null != optionsList && optionsList.length > 0) {
            for (String option : optionsList) {
                this.serverBootstrap.setOption(option, true);
            }
        }
//		this.serverBootstrap.setOption("receiveBufferSize", Constants.SO_RCVBUF);
//		this.serverBootstrap.setOption("sendBufferSize", Constants.SO_SNDBUF);
        /*
         * OptionName					SocketOptions
         * ****************************************************************************************
         * "keepAlive"					SO_KEEPALIVE
         * "reuseAddress"				SO_REUSEADDR
         * "soLinger"					SO_LINGER
         * "tcpNoDelay"					TCP_NODELAY
         * "receiveBufferSize"			SO_RCVBUF
         * "sendBufferSize"				SO_SNDBUF
         * "trafficClass"				java.net.Socket.setTrafficClass(int).
         * ****************************************************************************************
         * SO_KEEPALIVE			为 TCP 套接字设置 keepalive 选项时，如果在 2 个小时（注：实际值与实现有关）内在
         * 						任意方向上都没有跨越套接字交换数据，则 TCP 会自动将 keepalive 探头发送到
         * 						同位体。此探头是同位体必须响应的 TCP 段。期望的响应为以下三种之一：
         * 						1. 同位体通过期望的 ACK 响应。不通知应用程序（因为一切正常）。
         * 						在另一个 2 小时的不活动时间过后，TCP 将发送另一个探头。
         * 						2. 同位体通过 RST 响应，通知本地 TCP 同位体主机已崩溃并重新启动。套接字被关闭。
         * 						3. 同位体没有响应。套接字被关闭。 此选项的目的是检测同位体主机是否崩溃。
         * 						仅对 TCP 套接字（SocketImpl）有效。
         * ----------------------------------------------------------------------------------------
         * SO_REUSEADDR			设置套接字的 SO_REUSEADDR。在 java 中，它仅用于 MulticastSocket，
         * 						默认情况下为 MulticastSocket 设置此选项。 对 DatagramSocketImpl 有效。
         * ----------------------------------------------------------------------------------------
         * SO_LINGER			指定关闭时逗留的超时值。此选项启用/禁用从 TCP Socket 的 close() 立即返回。
         * 						启用具有非零 Integer 超时值 的此选项意味着 close() 在传输和确认写入到
         * 						同位体的所有数据前将一直阻塞，在那时套接字将从容地 关闭。到达逗留超时值时，
         * 						套接字将通过 TCP RST 强制性 关闭。启用超时值为零的选项将立即强制关闭。
         * 						如果指定的超时值大于 65,535，则其将被减少到 65,535。 仅对 TCP（SocketImpl）有效
         * ----------------------------------------------------------------------------------------
         * TCP_NODELAY			对此连接禁用 Nagle 算法。在确认以前的写入数据之前不会缓冲写入网络的数据。
         * 						仅对 TCP (SocketImpl) 有效。
         * 						Nagle算法试图减少TCP包的数量和结构性开销，将多个较小的包组合成较大的包进行发送。
         * 						但这不是重点。关键是这个算法受TCP延迟确认影响，会导致相继两次向连接发送请求包，
         * 						读数据时会有一个最多达500毫秒的延时。这叫做“ACK delay”。
         *						解决办法是设置TCP_NODELAY。
         * ----------------------------------------------------------------------------------------
         * SO_RCVBUF			设置传入网络 I/O 的平台所使用基础缓冲区的大小的提示。在设置方法中使用时，
         *						这是一个对应用程序内核的建议，关于通过套接字接收数据时使用的缓冲区大小。
         *						在获取方法中使用时，它必须返回在此套接字上接收数据时平台实际使用的缓冲区大小。
         *						对所有套接字（SocketImpl、DatagramSocketImpl）都有效。
         * ----------------------------------------------------------------------------------------
         * SO_SNDBUF			设置传出网络 I/O 的平台所使用的基础缓冲区大小的提示。在设置方法中使用时，
         * 						这是一个对应用程序内核的建议，关于通过套接字发送数据时使用的缓冲区大小。
         * 						在获取方法中使用时，它必须返回在此套接字上发送数据时平台实际使用的缓冲区大小。
         * 						对所有套接字（SocketImpl、DatagramSocketImpl）都有效。
         * ----------------------------------------------------------------------------------------
         * Socket.setTrafficClass(int)	为从此 Socket 上发送的包在 IP 头中设置流量类别 (traffic class)
         * 						或服务类型八位组 (type-of-service octet)。由于底层网络实现可能忽略此值，
         * 						应用程序应该将其视为一种提示。 tc 的值必须在 0 <= tc <= 255 范围内，
         * 						否则将抛出 IllegalArgumentException
         * ----------------------------------------------------------------------------------------
         */
    }

    public int getPortNumber(String[] args) {
        if (null == args || args.length < 1) {
            return this.portNumber;
        }

        try {
            return Integer.parseInt(args[0]);
        } catch (NumberFormatException e) {
            LOG.error("Exception occurred while " + "trying to parse the port number: {}", args[0]);
            LOG.error("NumberFormatException: {}", e);
            throw e;
        }
    }

    @Override
    public ChannelPipelineFactory getPipelineFactory() {
        return this.pipelineFactory;
    }

    @Override
    public void setPipelineFactory(ChannelPipelineFactory factory) {
        this.pipelineFactory = factory;
    }

    @Override
    public Bootstrap getServerBootstrap() {
        return this.serverBootstrap;
    }

    @Override
    public void setServerBootstrap(Bootstrap serverBootstrap) {
        this.serverBootstrap = serverBootstrap;
    }

    public int getPortNumber() {
        return this.portNumber;
    }

    public void setPortNumber(int portNumber) {
        this.portNumber = portNumber;
    }

    public InetSocketAddress getSocketAddress() {
        return this.socketAddress;
    }

    public void setSocketAddress(InetSocketAddress socketAddress) {
        this.socketAddress = socketAddress;
    }

    @Override
    public String toString() {
        return "NettyServer [socketAddress=" + this.socketAddress + ", portNumber=" + this.portNumber + "]";
    }

}
